#ifndef GSLAM_TILEMANAGER_H
#define GSLAM_TILEMANAGER_H

#include <GSLAM/core/GSLAM.h>
#include <GSLAM/core/TileProjection.h>
#include <list>

namespace GSLAM {

class TileBase: public GObject
{
public:
    virtual std::string type()const{return "TileBase";}
    virtual GImage  getTileImage(){return GImage();}
    virtual GImage  getTileDem(){return GImage();}
    virtual GImage getTileWeight() {return GImage(); }
    virtual GImage getTileQuad() {return GImage();}
    virtual void   setTileQuad(GImage& quad) {return;}
    virtual unsigned int getTileTexid() const {return -1;}
    virtual void setTileTexid(unsigned int texid) { return;}

    virtual Point3i getTilePosition(){return Point3i(0,0,-1);}// invalid when z<0, WARNING: NOT ALWAYS VALID!
    virtual double  getTimeStamp(){return -1;}// invalid when <0, WARNING: NOT ALWAYS VALID!
    virtual int     memSizeInBytes(){return -1;}// invalid when <0
    virtual bool    toStream(std::ostream& ostream){return false;}
    virtual bool    fromStream(std::istream& istream){return false;}

    virtual bool    modified(void) { return false; }
    virtual void    setModified(bool modifed=true) {}

    virtual void    release(void) { }

    static int64_t hashVal(int _x, int _y, int _z)
    {
        int64_t iz = _z, iy = _y, ix = _x;
        int64_t v = iz << 48 | iy << 24 | ix;
        return v;
    }

    class OutStream{
    public:
        OutStream(std::ostream& out):_out(out){}

        template <typename T>
        OutStream& operator <<(const T& obj){
            T tmp=obj;
            _out.write((char*)&tmp,sizeof(tmp));
            return *this;
        }

        template <typename T>
        OutStream& operator <<(const std::vector<T>& obj){
            *this<<(size_t)obj.size();
            for(const T& t:obj) *this<<t;
            return *this;
        }

        OutStream& operator <<(const GSLAM::GImage& obj){
            *this<<obj.cols<<obj.rows<<obj.flags;
            _out.write((const char*)obj.data,obj.total()*obj.elemSize());
            return *this;
        }

        OutStream& operator <<(const std::string& obj){
            *this<<obj.size();
            _out.write(obj.data(),obj.size());
            return *this;
        }

        std::ostream& _out;
    };

    class InStream{
    public:
        InStream(std::istream& in):_in(in){}

        template <typename T>
        InStream& operator <<(T& obj){
            _in.read((char*)&obj,sizeof(obj));
            return *this;
        }

        template <typename T>
        InStream& operator <<(std::vector<T>& obj){
            size_t sz=0;
            *this<<sz;
            obj.resize(sz);
            for(int i=0;i<sz;i++) *this<<obj[i];
            return *this;
        }

        InStream& operator <<(GSLAM::GImage& obj){
            int cols=0,rows=0,flags=0;
            *this<<cols<<rows<<flags;
            obj=GSLAM::GImage::create(rows,cols,flags);
            _in.read((char*)obj.data,obj.total()*obj.elemSize());
            return *this;
        }

        InStream& operator <<(std::string& obj){
            size_t sz=0;
            *this<<sz;
            obj.resize(sz);
            _in.read((char*)obj.data(),obj.size());
            return *this;
        }

        std::istream& _in;
    };

    class SizeStream{
    public:
        SizeStream(size_t sz=0):_size(sz){}

        template <typename T>
        SizeStream& operator <<(const T& obj){
            _size+=sizeof(obj);
            return *this;
        }

        template <typename T>
        SizeStream& operator <<(const std::vector<T>& obj){
            *this<<(size_t)obj.size();
            for(const T& t:obj) *this<<t;
            return *this;
        }

        SizeStream& operator <<(const GSLAM::GImage& obj){
            *this<<obj.cols<<obj.rows<<obj.flags;
            _size+=obj.total()*obj.elemSize();
            return *this;
        }

        SizeStream& operator <<(const std::string& obj){
            *this<<obj.size();
            _size+=obj.size();
            return *this;
        }

        size_t _size;
    };
};

typedef SPtr<TileBase> TilePtr;

class ImageTile : public TileBase
{
public:
    ImageTile(const GImage& image=GImage()):_image(image), _modified(false) {}

    virtual std::string type()const{return "ImageTile";}
    virtual GImage getTileImage(){return _image;}

    virtual bool    modified(void) { return _modified; }
    virtual void    setModified(bool modifed=true) { _modified = modifed; }

    template <typename StreamT>
    void StreamOp(StreamT& s){
        s<<_image;
    }

    virtual int memSizeInBytes(){
        SizeStream sz;
        StreamOp(sz);
        return sz._size;
    }

    virtual bool    toStream(std::ostream& s)
    {
        OutStream O(s);
        StreamOp(O);
        return true;
    }

    virtual bool fromStream(std::istream& s)
    {
        InStream O(s);
        StreamOp(O);
        return true;
    }

    GImage  _image;
    bool    _modified;
};
typedef SPtr<ImageTile> ImageTilePtr;

class TerrainTile : public ImageTile
{
public:
    TerrainTile(const GImage& image=GImage(),const GImage& dem=GImage())
        :ImageTile(image),_dem(dem){}
    virtual std::string type()const{return "TerrainTile";}

    virtual GImage getTileDem() {return _dem;}
    virtual void setTileDem(const GImage& dem) {_dem = dem;}

    template <typename StreamT>
    void StreamOp(StreamT& s){
        s<<_image<<_dem;
    }

    virtual int memSizeInBytes(){
        SizeStream sz;
        StreamOp(sz);
        return sz._size;
    }

    virtual bool    toStream(std::ostream& s)
    {
        OutStream O(s);
        StreamOp(O);
        return true;
    }

    virtual bool fromStream(std::istream& s)
    {
        InStream O(s);
        StreamOp(O);
        return true;
    }

    GImage  _dem;
};
typedef SPtr<TerrainTile> TerrainTilePtr;

class TerrainTileWithInfo: public TerrainTile
{
  public:
    TerrainTileWithInfo(const GImage& image=GImage(),const GImage& dem=GImage(),
                        const Point3i& location=Point3i(0,0,-1),const double& time=-1)
        : TerrainTile(image,dem),_location(location),_timestamp(time){}

    virtual Point3i getTilePosition(){return _location;}// invalid when z<0
    virtual double  getTimeStamp(){return _timestamp;}// invalid when <0
    virtual void    setTimeStamp(double time) {_timestamp = time;}

    template <typename StreamT>
    void StreamOp(StreamT& s){
        s<<_image<<_dem<<_location<<_timestamp;
    }

    virtual int memSizeInBytes(){
        SizeStream sz;
        StreamOp(sz);
        return sz._size;
    }

    virtual bool    toStream(std::ostream& s)
    {
        OutStream O(s);
        StreamOp(O);
        return true;
    }

    virtual bool fromStream(std::istream& s)
    {
        InStream O(s);
        StreamOp(O);
        return true;
    }

    Point3i _location;
    double  _timestamp;
};
typedef SPtr<TerrainTileWithInfo> TerrainTileWithInfoPtr;

class TileManager: public GObject
{
public:
    TileManager():_projection(new MercatorProjection()){}
    struct TileArea{
    public:
        TileArea():level(0),min(INT32_MAX,INT32_MAX),max(INT32_MIN,INT32_MIN){}

        Point2i getSize()const{return max-min;}
        int     getLevel()const{return level;}
        Point2i getMin()const{return min;}
        Point2i getMax()const{return max;}

        void    setLevel(int level_){level=level_;}
        void    setMin(const Point2i& min_){min=min_;}
        void    setMax(const Point2i& max_){max=max_;}

        int     level;
        Point2i min,max;
    };

    virtual ~TileManager(){}
    virtual std::string type()const{return "TileManager";}

    virtual TilePtr getTile(int x,int y,int z){return TilePtr();}
    virtual bool    setTile(int x,int y,int z, const TilePtr& tile){return false;}
    virtual std::list<TilePtr> getAllTile() {return std::list<TilePtr>{};}
    virtual int     maxZoomLevel()const{return _area.level;}
    virtual int     minZoomLevel()const{return -1;}
    virtual bool    save(const std::string& file){return false;}

    virtual TileArea          getTileArea()const{return   _area;}
    virtual TileProjectionPtr getProjection()const{return _projection;}

    virtual void setTileArea(TileArea area){_area=area;}
    virtual void setProjection(TileProjectionPtr projection){_projection=projection;}
protected:
    TileArea _area;
    TileProjectionPtr _projection;
};
typedef SPtr<TileManager> TileManagerPtr;

}


#endif

